package cn.xiaobai.j2exe.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson2.JSONObject;
import javafx.scene.image.Image;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 资源文件获取
 *
 * @Author yangdaji
 * @Date 2023/12/26
 * @PackageName cn.xiaobai.j2exe.utils
 * @ClassName SourceUtils
 */
public class SourceUtils {

    private static final String CONF_PREFIX = "app/";
    private static final String STATIC_PREFIX = "static/";
    private static final String FXML_SUFFIX = ".fxml";
    private static final String PNG_IMG_SUFFIX = ".png";
    private static final String HTML_SUFFIX = ".html";
    private static final String CFG_SUFFIX = ".cfg";

    private static final Map<String, Path> FXMLNAME_MAP = new ConcurrentHashMap<>();

    private static final Map<String, Image> PNGIMG_MAP = new ConcurrentHashMap<>();

    private static final Map<String, Path> HTML_MAP = new ConcurrentHashMap<>();

    /**
     * app 配置文件
     */
    private static String appCfg;

    /**
     * 当前 jar 目录
     */
    private static String currentPath;

    static {
        if (isJar()) {
            initJarResources();
        } else {
            initResources();
        }
    }

    /**
     * 是否jar 文件
     *
     * @return
     */
    private static boolean isJar() {
        // 获取当前
        currentPath = URLDecoder.decode(SourceUtils.class.getProtectionDomain()
                .getCodeSource().getLocation().getPath(), StandardCharsets.UTF_8);
        // jar 包
        return currentPath.endsWith(".jar");
    }

    /**
     * 初始化扫描 fxml 文件
     */
    public static void initResources() {
        URL staticUrl = ClassLoader.getSystemClassLoader().getResource(STATIC_PREFIX);
        URL appUrl = ClassLoader.getSystemClassLoader().getResource(CONF_PREFIX);
        if (staticUrl != null) {
            List<Path> pathList = new ArrayList<>();
            try {
                Path staticPath = Paths.get(staticUrl.toURI());
                list(staticPath.toFile(), pathList);
                pathList.forEach(p -> {
                    if (p.toString().endsWith(FXML_SUFFIX)) {
                        // fxml
                        FXMLNAME_MAP.put(p.getFileName().toString(), p);
                    }
                    if (p.toString().endsWith(PNG_IMG_SUFFIX)) {
                        //  png
                        PNGIMG_MAP.put(p.getFileName().toString(), new Image(p.toString()));
                    }
                    if (p.toString().endsWith(HTML_SUFFIX)) {
                        //  html
                        HTML_MAP.put(p.getFileName().toString(), p);
                    }

                });
            } catch (Exception e) {
                System.err.println("fxml path read error : " + e.fillInStackTrace());
            }
        }

        if (appUrl != null) {
            try {
                Path appPath = Paths.get(appUrl.toURI());
                Files.list(appPath)
                        .filter(p -> p.toString().endsWith(CFG_SUFFIX))
                        .forEach(p -> {
                            try {
                                appCfg = IoUtil.read(new FileInputStream(p.toFile()), StandardCharsets.UTF_8);
                            } catch (FileNotFoundException e) {
                                System.err.println("app path read error : " + e.fillInStackTrace());
                            }
                        });
            } catch (Exception e) {
                System.err.println("app path read error : " + e.fillInStackTrace());
            }

        }
    }

    /**
     * 扫描jar 包中路径
     */
    private static void initJarResources() {
        File file = new File(currentPath);
        if (file.exists()) {
            File parentFile = file.getParentFile();
            File resFile = new File(parentFile, "res");
            CommandUtils.hideFile(resFile);
            // 资源文件不存在则将jar 内资源文件解压
            if (FileUtil.isEmpty(resFile)) {
                // jar 解压
                try (JarFile jarFile = new JarFile(file)) {
                    Enumeration<JarEntry> entries = jarFile.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry jarEntry = entries.nextElement();
                        String realName = jarEntry.getRealName();
                        // 过滤 static 目录下的所有文件
                        if (realName.contains(STATIC_PREFIX) && !jarEntry.isDirectory()) {
                            File finalOutFile = checkFileAndMake(realName, resFile, true);
                            byte[] readBytes = IoUtil.readBytes(jarFile.getInputStream(jarEntry));
                            IoUtil.write(new FileOutputStream(finalOutFile), true, readBytes);
                        }
                    }
                } catch (IOException e) {
                    System.err.println("unzip fail :" + e.fillInStackTrace());
                }
            }

            // 扫描 res 目录
            List<Path> pathList = new ArrayList<>();
            list(resFile, pathList);
            if (CollectionUtil.isNotEmpty(pathList)) {
                pathList.forEach(p -> {
                    String fileName = p.getFileName().toString();
                    if (fileName.endsWith(FXML_SUFFIX)) {
                        FXMLNAME_MAP.put(fileName, p);
                    }
                    if (fileName.endsWith(PNG_IMG_SUFFIX)) {
                        PNGIMG_MAP.put(fileName, new Image(p.toString()));
                    }
                    if (fileName.endsWith(HTML_SUFFIX)) {
                        HTML_MAP.put(fileName, p);
                    }
                });
            }
            initJarConf(parentFile);
        }

    }

    /**
     * 初始化配置
     */
    private static void initJarConf(File workPath) {
        File file = new File(currentPath);
        File confFile = new File(workPath, "app");
        File conf = new File(confFile, "j2exe.cfg");
        // 资源文件不存在则将jar 内资源文件解压
        if (FileUtil.isEmpty(confFile)) {
            // jar 解压
            try (JarFile jarFile = new JarFile(file)) {
                Enumeration<JarEntry> entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry jarEntry = entries.nextElement();
                    String realName = jarEntry.getRealName();
                    // 过滤 app 目录下的 j2exe 配置
                    if (realName.contains(CONF_PREFIX) && realName.endsWith(CFG_SUFFIX)
                            && !jarEntry.isDirectory()) {
                        File finalOutFile = checkFileAndMake(realName, confFile, false);
                        conf = finalOutFile;
                        byte[] readBytes = IoUtil.readBytes(jarFile.getInputStream(jarEntry));
                        IoUtil.write(new FileOutputStream(finalOutFile), true, readBytes);
                        break;
                    }
                }
            } catch (IOException e) {
                System.err.println("unzip fail :" + e.fillInStackTrace());
            }
        }
        appCfg = FileUtils.readDataToJson(conf, String.class);
    }

    /**
     * 检查文件并创建, 同时返回目标文件名称
     *
     * @param zipInFileName 压缩包内的文件名称, 带路径
     * @param sourceDir     原解压目录
     * @param isMakeSource  是否创建原文件
     * @return
     */
    private static File checkFileAndMake(String zipInFileName, File sourceDir, boolean isMakeSource) {
        // 隔离文件名
        int pathCount = zipInFileName.split("/").length;
        String finalName = zipInFileName;
        while (--pathCount >= 0) {
            int beginIndex = finalName.indexOf("/") + 1;
            finalName = finalName.substring(beginIndex);
        }
        if (isMakeSource) {
            // 分割掉文件名, 保留路径
            int fileNameIndex = zipInFileName.lastIndexOf("/") + 1;
            String resPath = zipInFileName.substring(0, fileNameIndex);
            File mkdirFile = new File(sourceDir, resPath);
            if (!mkdirFile.exists()) {
                // 保证目录必须存在
                mkdirFile.mkdirs();
            }
            return new File(mkdirFile, finalName);
        }
        if (!sourceDir.exists()) {
            sourceDir.mkdirs();
        }
        // 构造整体的文件路径
        return new File(sourceDir, finalName);
    }

    private static void list(File dirFile, List<Path> pathList) {
        if (dirFile.exists() && dirFile.isDirectory()) {
            File[] listFiles = dirFile.listFiles();
            // 存在文件
            if (ArrayUtil.isNotEmpty(listFiles)) {
                for (File listFile : listFiles) {
                    list(listFile, pathList);
                }
            }
        } else {
            String name = dirFile.getName();
            if (name.endsWith(FXML_SUFFIX) ||
                    name.endsWith(PNG_IMG_SUFFIX) ||
                    name.endsWith(HTML_SUFFIX)) {
                pathList.add(Paths.get(dirFile.getAbsolutePath()));
            }

        }
    }

    /**
     * 获取 fxml url
     *
     * @param fxmlName fxml 名称
     * @return
     */
    public static URL getFxml(String fxmlName) {
        try {
            return FXMLNAME_MAP.get(fxmlName).toUri().toURL();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    /**
     * 获取 png image 对象
     *
     * @param pngName png 名称
     * @return
     */
    public static Image getPngImg(String pngName) {
        return PNGIMG_MAP.get(pngName);
    }


    /**
     * 获取资源 url
     *
     * @param name 资源名称
     * @return
     */
    public static URL getHtml(String name) {
        try {
            return HTML_MAP.get(name).toUri().toURL();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    /**
     * 获取 app 配置
     *
     * @param cls
     * @param <T>
     * @return
     */
    public static <T> T getAppCfg(Class<T> cls) {
        return JSONObject.parseObject(appCfg, cls);
    }

}
